// =======================================================================
// 
//       Filename:  plodder.cpp
// 
//    Description:  
// 
//        Version:  1.0
//        Created:  27.03.2010 23:16:24
//       Revision:  none
//       Compiler:  g++
// 
//         Author:  Dominik 'domachine' Burgdörfer (-), dominik.burgdoerfer@googlemail.com
//        Company:  -
// 
// =======================================================================

#include "plodder.hpp"
#include "processor.hpp"
#include "pluginmanager.hpp"
#include "page.hpp"
#include "utils/comparenodes.hpp"
#include "templatefile/file.hpp"
#include "syntaxerror.hpp"
#include "navigation/node.hpp"
#include <string.h>
#include <vector>
#include <boost/regex.hpp>
#include <unistd.h>
#include <fstream>
#include <cerrno>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>

#define foreach BOOST_FOREACH

namespace fs = boost::filesystem;
using namespace std;
using namespace boost;

namespace webplodder {
    Plodder::Plodder(const fs::path& configDir)
        : m_configDir(configDir), 
          m_cacheMode(templatefile::Parser::CACHE_ALL)
    {

    }

    Plodder::~Plodder() {
    }

    void Plodder::init()
    {
        fs::path configFile = m_configDir / "config.yaml";

        // Open config file and parse it
        ifstream in(configFile.string().c_str());
        if(in.good()) {
            YAML::Parser parser(in);

            YAML::Node doc;

            parser.GetNextDocument(doc);
            YAML::Iterator it;

            string key;

            for(it = doc.begin(); it != doc.end(); ++it) {
                it.first() >> key;
                it.second() >> m_scriptConfig[key];
            }

            in.close();
        }

        // Pass the parsed config to the Processor
        m_processor = shared_ptr<Processor>(
                new Processor(m_scriptConfig));
        shared_ptr<PluginManager> pluginManagerPtr(
                new PluginManager(m_configDir / "plugins"));
        
        pluginManagerPtr->scanPlugins();
        m_processor->setParent(this);
        m_processor->installPluginManager(
                                pluginManagerPtr);
    }

    void Plodder::scanDir(const fs::path& dir) 
    {
        
        m_targetPath = dir;
        if(chdir(dir.string().c_str()) < 0) {
            perror("unable to change directory");
            return;
        }

        // Open the given directory and search it for
        // page files
        fs::recursive_directory_iterator dIter(".");
        fs::recursive_directory_iterator endIter;
        vector<Page> pageFiles;
        fs::path templateFile;
        regex pageFileRegex("^.+\\.page$");
        vector<navigation::Node> navi;

        for(; dIter != endIter; ++dIter) {
            if(fs::is_regular_file(dIter->status()) &&
                    dIter->path().extension() == ".page") {
                fs::path outFile(dIter->path());
                outFile.replace_extension("html");
                Page pageFile(dIter->path(), 
                        outFile);

                try {
                    // Parse the current page file ...
                    pageFile.parse();
                }
                catch(YAML::Exception& e) {
                    cerr << pageFile.sourcePath() << ": error: "
                        << e.what() << endl;
                    printParsingError(pageFile.sourcePath(),
                                      e.what());
                    return;
                }

                if(pageFile["in_menu"].empty())
                    pageFile["in_menu"] = "1";
                

                int pos;
                if(! pageFile["pos"].empty())
                    pos = atoi(pageFile["pos"].c_str());

                if(pageFile["in_menu"] == "1")
                    navi.push_back(navigation::Node(pageFile["title"],
                                        pageFile.outputPath().string(),
                                        pos));

                pageFiles.push_back(pageFile);
            }
            else if(fs::is_regular_file(dIter->status()) &&
                    dIter->path().filename() == "default.template")
            {
                templateFile = dIter->path();
            }
        }

        if(templateFile.empty())
            throw runtime_error("No template file found");

        if(pageFiles.empty())
            throw runtime_error("No page files found");

        // Sort navigation
        sort(navi.begin(),
                navi.end(), utils::compareNodes);

        // Install all navigations
        foreach(Page& file, pageFiles)
            file.installNavigation(navi);

        // Construct a templatefile::File object
        // upon the source file and parse it
        templatefile::File cTemplateFile(templateFile);
        templatefile::Parser parser;

        parser.setCacheMode(m_cacheMode);

        try {
            parser.parse(cTemplateFile);
        }
        catch(SyntaxError& e) {
            printParsingError(templateFile,
                              e.what(), e.line());
            return;
        }

        // Iterate through the page queue
        foreach(Page pageFile, pageFiles)
        {
            
            // ... and pass it to the Processor
            m_processor->process(cTemplateFile, pageFile);
            
        }
    }

    void Plodder::setCacheMode(templatefile::Parser::CacheMode cacheMode)
    {
        m_cacheMode = cacheMode;
    }

    void Plodder::printParsingError(const fs::path& filePath,
                           const string& what,
                           int line)
    {
        fs::path printablePath(m_targetPath);
        fs::path::iterator filePathIter = filePath.begin();
        ++filePathIter;
        
        for(; filePathIter != filePath.end(); ++filePathIter) {
            printablePath /= *filePathIter;
        }
        
        cerr << printablePath.string()
            << ":";

        if(line > -1)
            cerr << line << ":";

        cerr << " error: " << what << endl;
    }

    void Plodder::printWarning(const std::string& what,
                      int line,
                      const fs::path& filePath)
    {
        
        if(!filePath.empty()) {
            
            fs::path printablePath(m_targetPath);
            fs::path::iterator filePathIter = filePath.begin();
            ++filePathIter;
        
            for(; filePathIter != filePath.end(); ++filePathIter) {
                
                printablePath /= *filePathIter;
            }
        
            cerr << printablePath.string()
                << ":";

            if(line > -1)
                cerr << line << ":";
        }
        else {
            cerr << "webplodder:";
        }

        cerr << " warning: " << what << endl;
    }
}
